#ifndef __INTEGER_H__
#define __INTEGER_H__

#include <iostream>
#include <stdlib.h>
#include <sstream>

using namespace std;


class Integer
{
    public:
                    //  ~Integer();                                 //  DTOR by compiler
                        Integer(){}                                  //  default CTOR

                        template<typename T>
                        Integer(T _t);
                        Integer(const char* _str);
                        Integer(const string& _str);
                       // Integer(const Integer& _i);                 //  copy CTOR by compiler
//        const Integer&  operator=(const Integer& _i);               //  Assignment operator by compiler

        //              Casting
                template<typename T>
                    operator T() const;

                    operator string() const;

        //              FRIENDS
        friend ostream& operator<<(ostream& _os, const Integer& _i);
        friend istream& operator>>(istream& _is, Integer& _i);

        //              MATH operators

        Integer&      operator+=(const Integer& _i);
        Integer&      operator-=(const Integer& _i);
        Integer&      operator*=(const Integer& _i);
        Integer&      operator/=(const Integer& _i);
        Integer&      operator%=(const Integer& _i);

        //              UNARY operators
        const Integer&  operator++();
        Integer         operator++(int);
        const Integer&  operator--();
        Integer         operator--(int);

        //              BITWISE operators
        const Integer&  operator<<=(const Integer& _i);
        const Integer&  operator>>=(const Integer& _i);
        const Integer&  operator|=(const Integer& _i);
        const Integer&  operator&=(const Integer& _i);
        const Integer&  operator^=(const Integer& _i);

        Integer         operator~(void) const;

        //              LOGICAL operators
        bool            operator==(const Integer& _i) const;
        bool            operator<=(const Integer& _i) const;
        bool            operator>=(const Integer& _i) const;
        bool            operator<(const Integer& _i) const;
        bool            operator>(const Integer& _i) const;
        bool            operator!=(const Integer& _i) const;

        bool            less(const Integer& _i) const;

//        template<typename T>
//        friend bool     operator==(const T& _t, const Integer& _i) ;
//        template<typename T>
//        friend bool     operator<=(const T& _t, const Integer& _i) ;
//        template<typename T>
//        friend bool     operator>=(const T& _t, const Integer& _i) ;
//        template<typename T>
//        friend bool     operator<(const T& _t, const Integer& _i) ;
//        template<typename T>
//        friend bool     operator>(const T& _t, const Integer& _i) ;
//        template<typename T>
//        friend bool     operator!=(const T& _t, const Integer& _i) ;

        template<typename T>
        friend T&   operator+=(T& _t, const Integer& _i);
        template<typename T>
        friend T&   operator-=(T& _t, const Integer& _i);
        template<typename T>
        friend T&   operator*=(T& _t, const Integer& _i);
        template<typename T>
        friend T&   operator/=(T& _t, const Integer& _i);
        template<typename T>
        friend T&   operator%=(T& _t, const Integer& _i);

    private:
        int m_num;

};


template<typename T>            //  template CTOR from types
inline Integer::Integer(T _t)
{  m_num = _t;  }

template<typename T>            //  template cast
inline Integer::operator T() const
{    return m_num;  }

//  cast to string

inline Integer::operator string() const
{
    stringstream buf;
    string str;
    buf<< m_num;
    buf>>str;

    return str;
}

//  CTOR from char*
inline Integer::Integer(const char* _str)
{
    m_num = atoi(_str);
}


//  CTOR from string
inline Integer::Integer(const string& _str)
{
    m_num = atoi(_str.c_str());
}


inline Integer& Integer::operator+=(const Integer& _i)
{
    m_num += _i.m_num;
    return *this;
}

inline Integer& Integer::operator-=(const Integer& _i)
{
    m_num -= _i.m_num;
    return *this;
}

inline Integer& Integer::operator*=(const Integer& _i)
{
    m_num *= _i.m_num;
    return *this;
}

inline Integer& Integer::operator/=(const Integer& _i)
{
    m_num /= _i.m_num;
    return *this;
}

inline Integer& Integer::operator%=(const Integer& _i)
{
    m_num %= _i.m_num;
    return *this;
}

const Integer& Integer::operator<<=(const Integer& _i)
{
    m_num <<= _i.m_num;
    return *this;
}

const Integer& Integer::operator>>=(const Integer& _i)
{
    m_num >>= _i.m_num;
    return *this;
}

inline const Integer& Integer::operator|=(const Integer& _i)
{
    m_num |= _i.m_num;
    return *this;
}

inline const Integer& Integer::operator&=(const Integer& _i)
{
    m_num &= _i.m_num;
    return *this;
}

inline const Integer& Integer::operator^=(const Integer& _i)
{
    m_num ^= _i.m_num;
    return *this;
}


//          LOGICAL OPERATORS
inline bool Integer::operator==(const Integer& _i) const
{    return m_num == _i.m_num;  }

inline bool Integer::operator<=(const Integer& _i) const
{    return m_num <= _i.m_num;  }

inline bool Integer::operator>=(const Integer& _i) const
{    return m_num >= _i.m_num;  }

inline bool Integer::operator<(const Integer& _i) const
{    return m_num < _i.m_num;  }

bool            Integer::less(const Integer& _i) const
{    return m_num < _i.m_num;  }

inline bool Integer::operator>(const Integer& _i) const
{    return m_num > _i.m_num;  }

inline bool Integer::operator!=(const Integer& _i) const
{    return m_num != _i.m_num;  }

inline const Integer& Integer::operator++()
{
    ++m_num;
    return *this;
}

inline Integer Integer::operator++(int)
{
    return m_num++;
}

inline const Integer& Integer::operator--()
{
    --m_num;
    return *this;
}

inline Integer Integer::operator--(int)
{
    return m_num--;
}

Integer Integer::operator~(void) const
{
    return ~m_num;
}

//  FRIENDS functions
istream& operator>>(istream& _is, Integer& _i)
{   return _is >> _i.m_num;    }

ostream& operator<<(ostream& _os, const Integer& _i)
{    return _os<<_i.m_num;  }


//template<typename T>
//bool  operator==(const T& _t, const Integer& _i)
//{
//    return _t == _i.m_num;
//}
//
//template<typename T>
//bool  operator<=(const T& _t, const Integer& _i)
//{
//    return _t <= _i.m_num;
//}
//
//template<typename T>
//bool  operator>=(const T& _t, const Integer& _i)
//{
//    return _t >= _i.m_num;
//}
//
//template<typename T>
//bool  operator!=(const T& _t, const Integer& _i)
//{
//    return _t != _i.m_num;
//}
//
//template<typename T>
//bool  operator<(const T& _t, const Integer& _i)
//{
//    return _t < _i.m_num;
//}
//
//template<typename T>
//bool  operator>(const T& _t, const Integer& _i)
//{
//    return _t > _i.m_num;
//}
//



template<typename T>
T&   operator+=(T& _t, const Integer& _i)
{
    return _t+= _i.m_num;
}

template<typename T>
T&   operator-=(T& _t, const Integer& _i)
{
    return _t-= _i.m_num;
}

template<typename T>
T&   operator*=(T& _t, const Integer& _i)
{
    return _t*= _i.m_num;
}

template<typename T>
T&   operator/=(T& _t, const Integer& _i)
{
    return _t/= _i.m_num;
}

template<typename T>
T&   operator%=(T& _t, const Integer& _i)
{
    return _t%= _i.m_num;
}

Integer operator<<(const Integer& _i1, const Integer& _i2)
{
    Integer temp(_i1);
    return temp <<= _i2;
}

Integer operator>>(const Integer& _i1, const Integer& _i2)
{
    Integer temp(_i1);
    return temp >>= _i2;
}

Integer   operator+(const Integer& _i1, const Integer& _i2)
{
    Integer temp(_i1);
    return temp += _i2;
}

Integer   operator-(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 -= _i2;
}

Integer   operator*(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 *= _i2;
}

Integer   operator/(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 /= _i2;
}

Integer   operator%(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 %= _i2;
}


Integer   operator|(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 |= _i2;
}

Integer   operator&(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 &= _i2;
}

Integer   operator^(const Integer& _i1, const Integer& _i2)
{
    return (Integer)_i1 ^= _i2;
}


bool     operator==(const Integer& _i1, const Integer& _i2)
{
    return (!(_i1 < _i2) && !(_i2 < _i1));   // i1 >= i2 && i2 >= i1  ==> i1 == i2
}
bool     operator>=(const Integer& _i1, const Integer& _i2);

bool     operator<=(const Integer& _i1, const Integer& _i2)
{
    return (_i1 < _i2)|| (!(_i1 < _i2) && !(_i2 < _i1));   //  (<) && (>==)
}

bool     operator<(const Integer& _i1, const Integer& _i2)
{
    return _i1.less(_i2);
}

bool     operator>(const Integer& _i1, const Integer& _i2);
bool     operator!=(const Integer& _i1, const Integer& _i2);

#endif // __INTEGER_H__
